package nginx.clojure.jersey; import static nginx.clojure.MiniConstants.BODY; import static nginx.clojure.MiniConstants.HEADERS; import static nginx.clojure.MiniConstants.NGX_HTTP_INTERNAL_SERVER_ERROR; import static nginx.clojure.MiniConstants.REQUEST_METHOD; import static nginx.clojure.MiniConstants.SCHEME; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Application; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.SecurityContext; import nginx.clojure.NginxClojureRT; import nginx.clojure.NginxHttpServerChannel; import nginx.clojure.bridge.NginxBridge; import nginx.clojure.java.NginxJavaRequest; import org.glassfish.jersey.internal.MapPropertiesDelegate; import org.glassfish.jersey.server.ApplicationHandler; import org.glassfish.jersey.server.ContainerException; import org.glassfish.jersey.server.ContainerRequest; import org.glassfish.jersey.server.ContainerResponse; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.spi.ContainerResponseWriter; public class NginxJerseyContainer implements NginxBridge { protected ApplicationHandler appHandler; protected String appPath; protected Class[] appResources; protected ClassLoader bootLoader; protected ResourceConfig configure() { return new ResourceConfig(appResources); } @Override public void boot(Map<String, String> properties, ClassLoader loader) { appPath = properties.get("jersey.app.path"); bootLoader = loader; if (appPath == null) { appPath = ""; } String application = properties.get("jersey.app.Appclass"); if (application != null) { Class appClz = null; try { appClz = loader.loadClass(application.trim()); } catch (ClassNotFoundException e) { NginxClojureRT.log.warn("can not load jersey.app.Appclass %s", application, e); throw new RuntimeException("can not load jersey.app.Appclass " + e.getMessage(), e); } try { appHandler = new ApplicationHandler((Class<? extends Application>) appClz.newInstance()); } catch (Exception e) { throw new RuntimeException("can not create jersey.app.Appclass" + e.getMessage(), e); } }else { String res = properties.get("jersey.app.resources"); List<Class> clzList = new ArrayList<Class>(); if (res != null) { for (String clz : res.split(",") ) { try { clzList.add(loader.loadClass(clz.trim())); } catch (Throwable e) { NginxClojureRT.log.warn("can not load resource %s, skiping", clz, e); } } appResources = clzList.toArray(new Class[clzList.size()]); } if (appResources == null || appResources.length == 0){ NginxClojureRT.log.warn("no resource defined, property %s is null", "jersey.app.resources"); } appHandler = new ApplicationHandler(configure()); } } @Override public ClassLoader getClassLoader() { return bootLoader; } protected SecurityContext getSecurityContext(final Principal principal, final boolean isSecure) { return new SecurityContext() { @Override public boolean isUserInRole(final String role) { return false; } @Override public boolean isSecure() { return isSecure; } @Override public Principal getUserPrincipal() { return principal; } @Override public String getAuthenticationScheme() { return null; } }; } public static class NginxReponseWriter implements ContainerResponseWriter { protected boolean suspend = false; protected NginxHttpServerChannel sc; public NginxReponseWriter() { } public NginxReponseWriter(NginxHttpServerChannel sc) { this.sc = sc; } @Override public void commit() { try { sc.close(); } catch (IOException e) { NginxClojureRT.log.error("commit failure!", e); } } @Override public boolean enableResponseBuffering() { return true; } @Override public void failure(Throwable e) { NginxClojureRT.log.error("failure from jersey", e); try { sc.sendResponse(NGX_HTTP_INTERNAL_SERVER_ERROR); } catch (IOException e1) { NginxClojureRT.log.error("send error response failed!", e); } } @Override public void setSuspendTimeout(long timeOut, TimeUnit timeUnit) throws IllegalStateException { } @Override public boolean suspend(long timeOut, TimeUnit timeUnit, TimeoutHandler timeoutHandler) { return suspend = true; } @Override public OutputStream writeResponseStatusAndHeaders(long contentLength, ContainerResponse context) throws ContainerException { try { sc.sendHeader(context.getStatus(), context.getStringHeaders().entrySet(), true, false); } catch (IOException e) { throw new ContainerException("send header error!", e); } return new OutputStream() { @Override public void write(int b) throws IOException { write(new byte[]{(byte)b}, 0, 1); } @Override public void write(byte[] b, int off, int len) throws IOException { if (b == null) { throw new NullPointerException("byte[] can not be null"); } if (off + len > b.length) { throw new IndexOutOfBoundsException("buffer space is too small, off + len > b.length"); } sc.send(b, off, len, false, false); } @Override public void flush() throws IOException { sc.flush(); } @Override public void close() throws IOException { sc.close(); } }; } } @Override public Object[] handle(NginxJavaRequest req) throws IOException { final NginxHttpServerChannel sc = req.hijack(false); Map<String, Object> headers = (Map<String, Object>) req.get(HEADERS); URI baseUri; try { baseUri = new URI(new StringBuilder((String) req.get(SCHEME)) .append("://").append(headers.get("host")).append(appPath) .append(appPath.endsWith("/") ? "" : "/").toString()); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } URI requestUri = baseUri.resolve(req.getVariable("request_uri")); ContainerRequest cr = new ContainerRequest(baseUri, requestUri, ((String)req.get(REQUEST_METHOD)).toUpperCase(), getSecurityContext(null, false), new MapPropertiesDelegate()); cr.setEntityStream((InputStream)req.get(BODY)); MultivaluedMap<String, String> crHeaders = cr.getHeaders(); for (Entry<String, Object> en : headers.entrySet()) { Object v = en.getValue(); if (v == null) { continue; } if (v.getClass().isArray()) { crHeaders.put(en.getKey(), Arrays.asList((String[])v)); }else { crHeaders.putSingle(en.getKey(), (String)v); } } NginxReponseWriter writer = new NginxReponseWriter(sc); cr.setWriter(writer); appHandler.handle(cr); if (!writer.suspend) { sc.close(); } return null; } }